﻿using System;
using System.IO;
using System.Net;
using System.Reflection;
using VisualEsse;
using VisualEsse.DataEngine;
using System.Windows.Media.Imaging;
using System.Windows;
using MultiViewer.Display;
using Microsoft.Data.Caching;
using System.Runtime.Serialization.Formatters.Binary;

namespace MultiViewer
{
    public abstract class TiledEarthImage : TiledImage, IBoundToEarthImage, IDisposable
    {
        private static string additionalServerAddress;
        public static string AdditionalServerAddress
        {
            get { return additionalServerAddress; }
            set { additionalServerAddress = value; }
        }
        private ICache cache;

        static bool useVelocity = false;

        static DataCacheFactory myCacheFactory;
        static DataCache myDefaultCache;//кэш оперативной памяти

        private string mode = "h";

        public TiledEarthImage()
        {
            tileSize = new Size(256, 256);
            minLevel = 1;
            maxLevel = 22; // Max. level of details supported by Virtual Earth
            entireImage = new Rect(-180, -87.5, 180, 87.5);
            aspectRatio = 175 / 360.0;

  
        }

        public ICache Cache
        {
            get
            {
                if (cache == null)
                {
                    const int MaxCacheSize = 100000000; // About 100 Mbytes
                    string cachePath = Path.Combine(
                        Path.GetDirectoryName(Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName), UniqueCacheKey + "Cache");
                    cache = new XmlBasedCache(cachePath, MaxCacheSize);
                }
                return cache;
            }
        }

        public abstract string UniqueCacheKey { get; }

        public string Mode
        {
            get { return mode; }
            set { mode = value; }
        }
        public static void prepareClient()
        {
            useVelocity = true;
            //-------------------------
            // Configure Cache Client 
            //-------------------------

            //Define Array for 1 Cache Host
            try
            {
                Microsoft.Data.Caching.DataCacheServerEndpoint[] servers = new DataCacheServerEndpoint[1];

                //Specify Cache Host Details 
                //  Parameter 1 = host name
                //  Parameter 2 = cache port number
                //  Parameter 3 = cache service name
                servers[0] = new DataCacheServerEndpoint("localhost",
                                        22233, "DistributedCacheService");

                //Select Routing or Simple Client
                // False = Simple Client (no routing table)
                // True = Routing Client
                bool routingClient = false;

                //Select local cache if desired
                // True = Enable local cache
                // False = Disable local cache
                bool localCache = true;

                //Disable exception messages since this sample works on a cache aside
                DataCacheFactory.DisableLogSinks();

                //Pass configuration settings to cacheFactory constructor
                myCacheFactory = new DataCacheFactory(servers,
                    routingClient, localCache);


                //Get reference to named cache called "NamedCache1"
                myDefaultCache = myCacheFactory.GetCache("default");

                prepareRegion();
            }
            catch (DataCacheException ex)
            {
                //кэш уже создан или Velocity не запущен
                MessageBox.Show(ex.Message);
            }
        }

        public static void  prepareRegion()
        {
            string mvRegion = "MVRegion";
            try
            {
                myDefaultCache.CreateRegion(mvRegion, false);

            }
            catch (DataCacheException ex)
            {
                //такой регион уже создан
                ////try
                ////{
                //    myDefaultCache.RemoveRegion(mvRegion);
                ////}
                ////catch (DataCacheException cex)
                ////{
                ////}

                ////try
                ////{
                //    myDefaultCache.CreateRegion(mvRegion, false);
                ////}
                ////catch (DataCacheException cex)
                ////{
                ////}
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + " in the prepareRegion vrthod");
            }
        }

        public BitmapImage ImageFromBuffer(Byte[] bytes)
        {
            MemoryStream stream = new MemoryStream(bytes);
            BitmapImage image = new BitmapImage();
            image.BeginInit();
            image.StreamSource = stream;
            image.EndInit();
            return image;
        }

        public Byte[] BufferFromImage(BitmapImage imageSource)
        {
            Stream stream = imageSource.StreamSource;
            Byte[] buffer = null;
            if (stream != null && stream.Length > 0)
            {
                stream.Position = 0;
                using (BinaryReader br = new BinaryReader(stream))
                {
                    buffer = br.ReadBytes((Int32)stream.Length);
                }
            }

            return buffer;
        }

        public bool IsInCache(string key, ref BitmapImage image)
        {
            //
            //return false;
            //
            DataCacheItemVersion dumref = null;
            bool bRetVal = false;
            byte[] byteImage = null;
            if ((byteImage = (byte[])myDefaultCache.Get(key, out dumref, "MVRegion")) != null)
            {
                image = ImageFromBuffer(byteImage);
                byteImage = null;
                dumref = null;
                bRetVal =  true;
            }
            else bRetVal = false;
            //GC.Collect();
            return bRetVal;
        }


        public void ToCache(string key, byte[] byteImage)
        {
            //
            //return;
            //
            DataCacheItemVersion dumref = null;
            try
            {
                if ((dumref = myDefaultCache.Add(key, byteImage, "MVRegion")) == null)
                    throw new Exception("Object did not add to cache");
            }
            catch (Exception ex)
            {
                string strMsg = ex.Message;
            }
            dumref = null;
        }

        public override BitmapImage GetTile(int row, int col, int level)
        {
            //prepareRegion();
            BitmapImage image = new BitmapImage();
            ICache cache = Cache;
            if (row < 0 || row >= (1 << level) ||
                col < 0 || col >= (1 << level) ||
                level < minLevel || level > maxLevel) // Return empty bitmap for out of range values 
                return new BitmapImage();//(tileSize.Width, tileSize.Height);
            string quadKey = TileToQuadKey(col, (1 << level) - row - 1, level);
            try
            {
                if (useVelocity && IsInCache(String.Concat(UniqueCacheKey, quadKey, mode), ref image))
                {
                    return image;
                }
                else
                {
                CacheKey cacheKey = new CacheKey(String.Concat(UniqueCacheKey, quadKey, mode));

                try
                {
                    Stream imageStream = cache.OpenFile(cacheKey, FileAccess.Read);
                    if (imageStream != null) // File is in cache
                    {
                        try
                        {
                            imageStream.Position = 0;
                            byte[] byteImg = new byte[imageStream.Length] ;
                            imageStream.Read(byteImg, 0, (int) imageStream.Length);
                            imageStream.Position = 0;
                            BitmapImage retImage = BitmapFromStream(imageStream);
                            try
                            {
                                if(useVelocity)
                                    ToCache(String.Concat(UniqueCacheKey, quadKey, mode), byteImg);
                                byteImg = null;
                            }
                            catch (Exception ex)
                            {
                                string strMsg = ex.Message;
                            }
                            //GC.Collect();
                            return retImage;
                        }
                        finally
                        {
                            try
                            {

                                if (imageStream.CanRead)
                                {   //imageStream.Close();
                                    imageStream.Dispose();
                                }
                                imageStream = null;
                            }
                            catch (Exception ex)
                            {
                                string strMsg = ex.Message;
                                //already closed
                            }
                            //GC.Collect();
                        }
                        // TODO: invalidate cache item in case of exception
                    }
                    else
                    {
                        WebClient webClient = new WebClient();
                        byte[] data = webClient.DownloadData(GetUrl(quadKey));
                        Stream cacheStream = cache.OpenOrCreateFile(cacheKey, FileAccess.Write);
                        MemoryStream ms = new MemoryStream(data);
                        ms.Position = 0;
                        BitmapImage tile = BitmapFromStream(ms);
                        ms.Position = 0;
                        Utils.CopyStream(cacheStream, ms);
                        cacheStream.Close();
                        ms.Close();
                        if(useVelocity)
                            ToCache(String.Concat(UniqueCacheKey, quadKey, mode), data);
                        data = null;
                        if (cacheStream.CanRead)
                        {
                            cacheStream.Dispose();
                        }
                        cacheStream = null;
                        //GC.Collect();
                        return tile;
                    }
                }
                catch (Exception exc)
                {
                    return new BitmapImage(/*tileSize.Width, tileSize.Height*/);
                    // TODO: return predefined bitmap with error picture
                }
                }
            }
            catch
            {
                return new BitmapImage();
            }
        }
        public override BitmapImage GetAdditionalTile(int row, int col, int level)
        {
            string quadKey = TileToQuadKey(col, (1 << level) - row - 1, level);
            WebClient webClient = new WebClient();
            byte[] data = webClient.DownloadData(GetAdditionalUrl(quadKey));
            MemoryStream ms = new MemoryStream(data);
            ms.Position = 0;
            BitmapImage tile = BitmapFromStream(ms);
            ms.Position = 0;
            ms.Close();
            data = null;
            return tile;
        }
        public void StoreCache()
        {
            if(cache != null) 
            {
                XmlBasedCache xc = cache as XmlBasedCache;
                xc.StoreXml();
            }
        }

        private BitmapImage BitmapFromStream(Stream stream)
        {
            BitmapImage bmp = new BitmapImage();

            bmp.BeginInit();
            bmp.StreamSource = stream;
            bmp.CacheOption = BitmapCacheOption.OnLoad;
            bmp.EndInit();

            return bmp;
        }

        public override void Dispose()
        {
            if (cache != null)
            {
                cache.Dispose();
                cache = null;
            }
        }

        private static string TileToQuadKey(int tx, int ty, int zl)
        {
            string quad = "";
            for (int i = zl; i > 0; i--)
            {
                int mask = 1 << (i - 1);
                int cell = 0;
                if ((tx & mask) != 0)
                {
                    cell++;
                }
                if ((ty & mask) != 0)
                {
                    cell += 2;
                }
                quad += cell;
            }
            return quad;
        }

        protected abstract string GetUrl(string quadKey);
        protected abstract string GetAdditionalUrl(string quadKey);

        #region IBoundToEarthImage Members

        public VisualEsse.DataEngine.Types.GeoRect GetTileRect(int row, int col, int level)
        {
            float lon = -180 + (float)col / (float)(1 << level) * 360;

            int y = (int)(row * 256);
            float lat2 = CoordinateConverter.YToLatitudeAtZoom(y, level);

            float lon2 =  -180 + (float)(1 + col) / (float)(1 << level) * 360;
            float lat = CoordinateConverter.YToLatitudeAtZoom((row + 1) * 256, level);

            return new VisualEsse.DataEngine.Types.GeoRect(lon, lon2, lat, lat2);
        }

        public Projection Projection
        {
            get { return Projection.Mercator; }
        }

        #endregion
    }

    public class VirtualEarthImage : TiledEarthImage
    {
        protected override string GetUrl(string quadKey)
        {
            string mapType = Mode; // or "a" for Aerial
            string mapExtension = ".png";

            //TODO what is the ?g= hanging off the end 1 or 15?
            // -- comment above was left by the article author. funny enough to keep it
            string url = String.Concat(new object[] { "http://", mapType, quadKey[quadKey.Length - 1], ".ortho.tiles.virtualearth.net/tiles/", mapType, quadKey, mapExtension, "?g=", 1 });
            return url;
        }
        protected override string GetAdditionalUrl(string quadKey)
        {
            string url = String.Format(AdditionalServerAddress, quadKey);
            return url;
        }

        public override string UniqueCacheKey
        {
            get { return "VE"; }
        }
    }

    public class CustomTiledImage : TiledEarthImage
    {
        private string uniqueId;
        private string urlTemplate;

        public CustomTiledImage(string uniqueId, string urlTemplate)
        {
            this.uniqueId = uniqueId;
            this.urlTemplate = urlTemplate;
        }

        protected override string GetUrl(string quadKey)
        {
            string url = urlTemplate.Replace("{0}",quadKey);
            // url = String.Format("http://arca7.wdcb.ru/WMSTiles/tiles/10/{0}.png", quadKey);
            return url;
        }
        protected override string GetAdditionalUrl(string quadKey)
        {
            string url = String.Format(AdditionalServerAddress, quadKey);
            return url;
        }

        public override string UniqueCacheKey
        {
            get { return "Custom" + uniqueId; }
        }
    }
}